Skip to content

Conversation

@birkskyum
Copy link
Member

@birkskyum birkskyum commented Aug 13, 2025

Add ESM output, next to the UMD/AMD modules.

Closes #1595

Screen.Recording.2025-08-13.at.22.34.23.mov

I've converted below examlple to use the new ESM bundle
http://localhost:9966/test/examples/filter-layer-symbols-using-global-state.html

Launch Checklist

  • Confirm your changes do not include backports from Mapbox projects (unless with compliant license) - if you are not sure about this, please ask!

@birkskyum birkskyum mentioned this pull request Aug 13, 2025
@birkskyum birkskyum marked this pull request as ready for review August 14, 2025 03:04
@birkskyum birkskyum requested a review from HarelM August 14, 2025 03:10
@HarelM
Copy link
Collaborator

HarelM commented Aug 14, 2025

I think this setup assumes a bit too much about where the worker code file is.
I think a middle ground would be to immitate the csp idea in terms of setup and have that support both esm and cjs.
It means the user would need to specify where the worker code is, instead of the current assumption that it is next to the main thread code. I think it will also make sure that it will be supported with all kinds of bundlers.
This is similar to RTL plugin and CSP and I think it's a smaller change, and had better chances to work correctly.

@birkskyum
Copy link
Member Author

birkskyum commented Aug 14, 2025

@HarelM , i've split it out so that the worker url is set explicitly

https://github.com/maplibre/maplibre-gl-js/pull/6254/files#diff-538d7808b52996feefd0ff81d905bfb0dbcc1351484e2d255261769ebdb1f362R43-R48

I've also added build tests to make sure the esm build is actually esm (they don't go deep though, because i.e. murmurhash.js does have module.export still, but the outermost layer is esm)

@HarelM
Copy link
Collaborator

HarelM commented Aug 14, 2025

This looks great! Thanks!
I've added a few minor comments.

@HarelM
Copy link
Collaborator

HarelM commented Aug 14, 2025

I would recommend also to use npm run pack and check the output in another project with bundling etc.
The fact that this now has esm might be the default for some bundlers and might cause them to pack only the esm part of the main thread instead of the cjs and cause app to stop working, so this needs to be tested as well.
You can use npx-maplibre-gl as an angular project to test and maybe Maputnic as react one.

@birkskyum
Copy link
Member Author

I've tried npm pack, and loading it into maputnik node_modules, which appear to work fine. I believe it's still just using the .js version, rather than the new .mjs

@birkskyum
Copy link
Member Author

birkskyum commented Aug 15, 2025

ngx-maplibre-gl appear to pick up the esm module in the showcase, so it'll only work if the showcase/main.ts file has the setWorkerUrl added - otherwise the map will just be the light blue background without data.

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
import * as maplibregl from 'maplibre-gl';

// Set the worker URL for MapLibre GL when using ESM version
maplibregl.setWorkerUrl('/maplibre-gl/maplibre-gl-worker.mjs');

bootstrapApplication(AppComponent, appConfig).catch((err) =>
  console.error(err)
);

@birkskyum
Copy link
Member Author

birkskyum commented Aug 15, 2025

Either we can use the auto-worker url like there was logic for previously, or this might have to come as a breaking change if the default will be an explicit setting of the worker url.

Or both, having the auto-url logic now, and requiring explicit url from next major.

@HarelM
Copy link
Collaborator

HarelM commented Aug 15, 2025

I don't think the auto-worker url will work for angular (webpack) unfortunately.
So I'm not sure there's a way around this a breaking change, which is ok as we haven't released a breaking change version this year, and we might need to consider this.
But if we are doing a breaking change version we need to collect breaking changes requirements like we did for v5.

@birkskyum
Copy link
Member Author

I think going ESM-only in v6 is the right call here.

<body>
<div id="map"></div>
<script type="module">
import * as maplibregl from '../../dist/maplibre-gl.mjs';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this will work in the docs' examples.
Can you check if a change is needed around here:

htmlContent = htmlContent.replace(/-dev.js/g, '.js');

import {plugins, watchStagingPlugin} from './build/rollup_plugins';
import banner from './build/banner';
import {type RollupOptions} from 'rollup';
import {config as cspConfig} from './rollup.config.csp';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a better name to the file and method is probably needed here.

const content = fs.readFileSync(esmPath, 'utf8');

// Check for ES module exports at the end of the file
expect(content).toMatch(/export\s+\{[^}]+\};\s*$/m);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super thrilled with checking the string content of the file, is there a better way to check this?

@HarelM
Copy link
Collaborator

HarelM commented Sep 17, 2025

I would expect to see more documentation on how to use this new ESM build, one of the places is probably here:
https://github.com/maplibre/maplibre-gl-js/blob/main/docs/index.md

But I think there might be a need for more descriptive information, maybe some info on packagers such as webpack and rollup...

@HarelM
Copy link
Collaborator

HarelM commented Sep 17, 2025

@robertgzr
Copy link

@louwers told me so!

@HarelM
Copy link
Collaborator

HarelM commented Nov 16, 2025

This is on the roadmap to version 6, which is the next breaking change version, it's just not clear when we would create a breaking change version...

@chrneumann
Copy link
Contributor

chrneumann commented Nov 28, 2025

I'm experimenting to use the ESM modules with Vite/React Router v7. If you need any feedback, just contact me.

Works so far like this:

import {  Map, setWorkerURL } from "maplibre-gl";
import webWorkerURL from "maplibre-gl/dist/maplibre-gl-worker.mjs?url";
setWorkerUrl(webWorkerURL);
const map = new Map(...);

Vite loads the CJS module when using SSR (as far as I understood because NodeJS resolution is used on the server side per default). To prevent that, one can set it up like this in vite.config.ts to use Vites resolution (which uses ESM per default):

export default defineConfig({
  ...,
  ssr: {
    noExternal: ['maplibre-gl'],
  },
});

Not sure if this is the best solution. "noExternal" might decrease module resolution performance on the server side.

@louwers
Copy link
Member

louwers commented Nov 28, 2025

@chrneumann The ?url search parameter is a non-standard Vite feature. This issue is about distributing an ESM build that can be used directly by browsers without a bundler.

@chrneumann
Copy link
Contributor

But it will also be used with bundlers? And this needs documentation (#6254 (comment))? Anyways, maybe it's helpful for someone who wants to use the ESM build with Vite as I do :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export ESM Bundle

5 participants